package br.unifor.g2cl.hl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.jgcs.JGCSException;
import net.sf.jgcs.Message;
import net.sf.jgcs.Service;
import net.sf.jgcs.membership.MembershipSession;
import br.unifor.g2cl.G2CLMessage;
import br.unifor.g2cl.GroupRequest;
import br.unifor.g2cl.IMarshalDataSession;
import br.unifor.g2cl.Util;
import br.unifor.g2cl.hl.listener.MessageDispatcherListener;
/**
 * Provides a way to make remote method invocation in the members of the group. Remote method invocations should be made following the policies defined on {@link GroupRequest}.
 * <br/><br/>
 * This service is part of the High-level group.
 * 
 * @author Leandro Sales (leandro.shp@gmail.com)
 */
public class RpcDispatcher extends MessageDispatcher implements
		MessageDispatcherListener {

	private static Logger logger = Logger.getLogger(RpcDispatcher.class
			.getName());

	private Object serverObject;
	
	private Marshaller reqMarshaller = null;

	
	public Marshaller getReqMarshaller() {
		return reqMarshaller;
	}

	public void setReqMarshaller(Marshaller reqMarshaller) {
		this.reqMarshaller = reqMarshaller;
	}


	public RpcDispatcher(	IMarshalDataSession marshalDataSession,
							MembershipSession membershipSession, 
							Service service,
							Object serverObject) throws JGCSException {

		super(marshalDataSession, membershipSession, service);
		setMessageDispatcherListener(this);
		this.serverObject = serverObject;
	}


	public RspList callRemoteMethod(MethodCall methodCall, int mode, int timeout) throws Exception {
		Message msg;
		msg = marshalDataSession.createMessage();
		msg.setSenderAddress(membershipSession.getLocalAddress());

		byte[] object = null;
		if (reqMarshaller != null) {
			object = reqMarshaller.getArrayFromObject(methodCall);
		} else {
			object = Util.getArrayFromObject(methodCall);
		}

		msg.setPayload(object);
		return castMessage(null, msg, mode, timeout);

	}

	public RspList callRemoteMethod(String method, Class[] paramTypes,
			Object[] args, int mode, int timeout) throws Exception {

		MethodCall methodCall = new MethodCall(method, paramTypes, args);
		return callRemoteMethod(methodCall, mode, timeout);
	}

	///////////////////////////////////////////////////////////////////////////


	public Object callRemoteMethod(SocketAddress target, MethodCall methodCall, int mode, int timeout)
	throws Exception {
		G2CLMessage msg;
		msg = (G2CLMessage) marshalDataSession.createMessage();
		msg.setSenderAddress(membershipSession.getLocalAddress());

		byte[] object = null;
		if (reqMarshaller != null) {
			object = reqMarshaller.getArrayFromObject(methodCall);
		} else {
			object = Util.getArrayFromObject(methodCall);
		}

		msg.setPayload(object);
		return sendMessage(msg, target, mode, timeout);
	}

	public Object callRemoteMethod(SocketAddress target, String method,
			Class[] paramTypes, Object[] args, int mode, int timeout)
	throws Exception {

		MethodCall methodCall = new MethodCall(method, paramTypes, args);
		return callRemoteMethod(target, methodCall, mode, timeout);
	}


	
	///////////////////////////////////////////////////////////////////////////
	
	public Object handle(G2CLMessage message) {
		try {
			byte[] messagePayload = message.getPayload();
			MethodCall methodCall = null;
			
			if (reqMarshaller != null) {
				methodCall = (MethodCall) reqMarshaller.getObjectFromByte(
						messagePayload, 0, messagePayload.length);	
			} else {
				methodCall = (MethodCall) Util.getObjectFromByte(
						messagePayload, 0, messagePayload.length);
			}
			
			return invokeMethod(methodCall);
			
		} catch (Exception e) {
			logger.log(Level.SEVERE, "error handling message", e);
		}
		return null;
	}
	
	private Object invokeMethod(MethodCall methodCall) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		Method m = null;
		try{
			m = serverObject.getClass().getMethod(methodCall.getName(), methodCall.getParamTypes());
		}
		catch (NoSuchMethodException e) {
			
			String wantedMethodName = methodCall.getName();
			Class[] wantedMethodParamTypes = methodCall.getParamTypes();
			
			Method[] serverObjectMethods = serverObject.getClass().getMethods();
			for (int methodIndex = 0; methodIndex < serverObjectMethods.length && m == null; methodIndex++) {
				
				Method method = serverObjectMethods[methodIndex];
				Class[] paramTypes = method.getParameterTypes();
				if (wantedMethodName.equals(method.getName()) && 
						paramTypes.length == wantedMethodParamTypes.length) {
					boolean methodFound = true;
					for (int paramIndex = 0; paramIndex < paramTypes.length && methodFound; paramIndex++) {
						methodFound = 	methodFound && 
										paramTypes[paramIndex].isAssignableFrom(wantedMethodParamTypes[paramIndex]);
					}
					if (methodFound) {
						m = method;
					}
				}
			}
		}
		
		return m.invoke(serverObject, methodCall.getArgs());
	}

}
